module PNEngine  
  class Arc < ActiveRecord::Base
    include Resource::InstanceMethods
    extend Resource::ClassMethods
    
    belongs_to :petrinet
    belongs_to :transition
    belongs_to :place
    
    # Constants used for case_transformation
    TAKE_PARENT_CASE = 1
    CREATE_CHILD_CASES = 2
    
    # Pushes token from transition to place. Resource must be saved afterwards
    def push(token)
      raise "Tokens can't be pushed over arcs coming from places." if place_to_transition?
      # Clone Token
      token = token.clone
      # Check for case_transformation == TAKE_PARENT_CASE
      if self.case_transformation == TAKE_PARENT_CASE
        if token.case.parent
          token.case = token.case.parent
        else
          raise("[#{log_name}] Token #{token.inspect} has no parent case.")
        end
      end
      # Calculate arc weight
      arc_weight = apply_arc_weight(token.value)
      # Apply arc transformatiom
      token.value = apply_arc_transformation(token.value)
      # Produce tokens depending on arc weight
      tokens = []
      arc_weight.downto(1) do |i|
        new_token = token.clone
        # Check for case_transformation == CREATE_CHILD_CASES
        new_token.case = Case.create!(:parent => new_token.case) if self.case_transformation == CREATE_CHILD_CASES
        #TODO arc weight is not always operating on node sets
        if weight_xpath
          new_token.value = REXML::XPath.first(REXML::Document.new(new_token.value),"#{weight_xpath}[position()=#{i}]").to_s
        end
        tokens << new_token
        #TODO place.push(new_token) really necessary?
        place.tokens << new_token
      end
      return tokens
    end
    
    #TODO method not needed, migrate db scheme from from_is_place to place_to_transition
    def place_to_transition?
      from_is_place
    end
    
    def initialize(from, to)
      if from.is_a?(Place) && to.is_a?(Transition)
        super(:place => from, :transition => to, :from_is_place => true)
      elsif from.is_a?(Transition) && to.is_a?(Place)
        super(:place => to, :transition => from, :from_is_place => false)
      else
        raise ArgumentError, "Arc: From (#{from.class}##{from.pid}) and to (#{to.class}##{to.pid}) aren't place *and* transition!"
      end
    end

    def base_path
      File.join(Merb::Config[:data_path], 'arc', id.to_s)
    end
		
    def xslt_path
      File.join(base_path, 'main_xslt.xsl')
    end	

    def from
      from_is_place ? place : transition
    end
    
    def to
      from_is_place ? transition : place
    end

    def to_pnml(as_document = false)
      pnml_doc = REXML::Document.new
      parc = pnml_doc.add_element('arc', {'id' => pid, 'source' => from.pid, 'target' => to.pid})
			
      if xslt_url
        toolspecific = parc.add_element('toolspecific', {'tool' => 'Petri Net Engine', 'version' => '1.0'})
				
        #add arc expression
        toolspecific.add_element('transformation', {'href' => xslt_url})
      end
		
      output = ''
      as_document ? pnml_doc : pnml_doc.write(output, 2, true)
    end
    
    protected
    def validate
      if self.from_is_place && self.case_transformation
        errors.add(:case_transformation, "Case transformation for incoming arcs not implemented yet.")
      end
    end
    
    #applies arc transformation on specified xml
    def apply_arc_transformation(xml)
      return xml unless xslt_url
      
      xslt_arc = XML::XSLT.new
      #to support empty tokens which are filled with content,
      #xslt is applied on default xml value <root/> if empty or nil
      xslt_arc.xml = REXML::Document.new(xml.blank? ? "<root/>" : xml)
      xslt_arc.xsl = xslt_path
      new_value = xslt_arc.serve
      Merb.logger.info("Executed XSLT for arc expression referenced by #{xslt_url}")  
      
      new_value
    end
    
    def apply_arc_weight(xml)
      return weight unless weight_xpath
      #TODO arc weight is not always operating on node sets
      #return XML.read_xpath(xml, weight_xpath).to_i
      return REXML::XPath.first(REXML::Document.new(xml),"count(#{weight_xpath})").to_i
    end
  end
  
  class ReadArc < Arc
  end

  class InhibitorArc < Arc
    #XXX Fix for Yasper, which interpretes reset arcs from transition to place
    before_create :check_arc_direction
    def check_arc_direction
      self.from_is_place = true
    end
  end
  
  class ResetArc < Arc
    #XXX Fix for Yasper, which interpretes reset arcs from transition to place
    before_create :check_arc_direction
    def check_arc_direction
      self.from_is_place = true
    end
  end
end
